/*
 * author: VDaras
 */

#include "RenderingManager.h"

#include "gui/Graphics.h"
#include "sdl/Application.h"
#include "sdl/Screen.h"
#include "sdl/Color.h"
#include "Globals.h"
#include "Logger.h"
#include "Utilities.h"
#include "PhysicsLogic.h"
#include <iostream>
#ifdef DSDL_GFX_ENABLED
#include <SDL/SDL_gfxPrimitives.h>
#endif

RenderingManager::RenderingManager():debugFont(NULL)
{
    m_screen = NULL;
    m_camera = NULL;
}


RenderingManager::~RenderingManager()
{
    EraseAll( );
}


/**
 * Initializes the Rendering Manager
 *
 * @param screen - handle to the game's screen
 * @param camera - handle to the game's camera
 */

void RenderingManager::Init(sdl::Canvas* screen, Camera* camera)
{
    m_screen = screen;
    m_camera = camera;

    //m_primitiveRenderingLayer.reset(new sdl::Canvas(m_screen->GetWidth(), m_screen->GetHeight()));    
}


void RenderingManager::AddLayer(const std::string& name, float parallaxFactor)
{
    std::string key( name );
    
    StrToUpper( key );
    
    RenderLayer *toAdd = new RenderLayer( parallaxFactor );

    //add layer pointer to the hash table
    m_layers.insert( std::pair<std::string, RenderLayer*>( key, toAdd ) );

    //find where to place layer based on parallax factor.
    std::vector<RenderLayer* >::iterator it = m_sortedLayers.begin();
    std::vector<RenderLayer* >::iterator end = m_sortedLayers.end();
    for(;it!=end;++it)
    {
        if((*it)->GetFactor() > parallaxFactor)
        {
            break;
        }
    }
    
    //add layer pointer to the sorted vector
    m_sortedLayers.insert( it, toAdd );
    
}


/**
 * Removes the selected layer. Drawables are not released.
 * @param layerName
 * @return 
 */
bool RenderingManager::LayerExists(const std::string& name)
{
    return FindLayer( name ) != NULL ? true : false;
}


/*
 *  Removes layer from map and vector. Returns false if layer doesn't exist
 */

bool RenderingManager::RemoveLayer(const std::string& name)
{
    RenderLayer *toRemove = FindLayer( name );

    if( toRemove != NULL )
    {
        //the following two RenderLayer pointers to be erased (from map and vector respectively),
        //point to the same heap object

        //erase layer from layers hash table
        m_layers.erase( name );

        //erase layer from sorted vector
        m_sortedLayers.erase( m_sortedLayers.begin( ) + LayerIndex( toRemove ) );

        //free memory from the heap
        delete toRemove;

        return true;
    }

    return false;
}


/*
 * Brings layer to front. Returns false if layer doesn't
 * exist.
 */

bool RenderingManager::BringToFront(const std::string &name)
{
    RenderLayer *toMove = FindLayer( name );

    if( toMove != NULL )
    {
        //find layer's current index in sorted layers vector
        int currentIndex = LayerIndex( toMove );

        //if we want to move a layer to the front, then the the is located @ (endingIndex - currentIndex) positions.
        Insertion( currentIndex, ( LayerCount( ) ) - currentIndex, RIGHT );
        return true;
    }

    return false;
}


/*
 * Sends layer to back. Returns false if layer doesn't
 * exist.
 */

bool RenderingManager::SendToBack(const std::string &layerName)
{
    RenderLayer *toMove = FindLayer( layerName );

    if( toMove != NULL )
    {
        //find layer's current index
        int currentIndex = LayerIndex( toMove );

        //if we want to move a layer to the back, then the back is located @ currentIndex positions.
        Insertion( currentIndex, currentIndex, LEFT );
        return true;
    }

    return false;
}


/*
 *  Moves a layer (positions) times forward. Returns false if
 *  layer doesn't exist.
 */

bool RenderingManager::BringForward(const std::string& layerName, unsigned int positions)
{
    RenderLayer *toMove = FindLayer( layerName );

    if( toMove != NULL )
    {
        Insertion( LayerIndex( toMove ), positions, RIGHT );
        return true;
    }

    return false;
}


/*
 *  Moves a layer (positions) times backwards. Returns false if
 *  layer doesn't exist.
 */

bool RenderingManager::SendBackward(const std::string& layerName, unsigned int positions)
{
    RenderLayer *toMove = FindLayer( layerName );

    if( toMove != NULL )
    {
        Insertion( LayerIndex( toMove ), positions, LEFT );
        return true;
    }

    return false;
}


/*
 * Change layer visibility. Return's false if layer doesn't exist
 */

bool RenderingManager::SetLayerVisiblity(const std::string& layerName, bool isVisible)
{
    RenderLayer *toAlter = FindLayer( layerName );

    if( toAlter != NULL )
    {
        toAlter->SetVisibility( isVisible );
        return true;
    }

    return false;
}


/*
 * Change the visibility of all layers.
 */

void RenderingManager::SetAllLayerVisibility(bool isVisible)
{
    for( unsigned int i = 0; i < m_sortedLayers.size( ); ++i )
    {
        m_sortedLayers[i]->SetVisibility( isVisible );
    }
}


/*
 * iterate layers and render them
 */

void RenderingManager::RenderLayers(sdl::GraphicsCore* gCore) const
{
#ifdef DEBUG
    if( m_screen == NULL )
    {
        LOG( Logger::CHANNEL_GRAPHICS, LogFileStream::LEVEL_ERROR ) << "Rendering Manager, uninitialized screen!";
        return;
    }

    if( m_camera == NULL )
    {
        LOG( Logger::CHANNEL_GRAPHICS, LogFileStream::LEVEL_ERROR ) << "Rendering Manager, uninitialized camera!";
        return;
    }
#endif

    for( unsigned int i = 0; i < m_sortedLayers.size( ); ++i )
    {
        m_sortedLayers[i]->Render( gCore, m_camera );
    }

}


/*
 * Erase all layers
 */

void RenderingManager::EraseAll()
{
    m_layers.erase( m_layers.begin( ), m_layers.end( ) );

    for( std::vector<RenderLayer *>::iterator iter = m_sortedLayers.begin( ); iter != m_sortedLayers.end( ); )
    {
        delete (*iter );
        iter = m_sortedLayers.erase( iter );
    }
}


/*
 * register drawable, returns false if layer doesn't exist
 */

bool RenderingManager::RegisterDrawable(Drawable *toRegister, const std::string &layerName)
{
    RenderLayer *result = FindLayer( layerName );

    if( result != NULL )
    {
        result->AddDrawable( toRegister );

	toRegister->SetRenderingManager( this );

        return true;
    }

    return false;
}


/*
 * unregister drawable, returns false if drawable doesn't exist
 */

bool RenderingManager::UnregisterDrawable(Drawable *toUnregister)
{
    std::map<std::string, RenderLayer *>::iterator iter;

    RenderLayer *current = NULL;
    for( iter = m_layers.begin( ); iter != m_layers.end( ); ++iter )
    {
        current = iter->second;
        if( current->ContainsDrawable( toUnregister ) )
        {
            current->RemoveDrawable( toUnregister );
            return true;
        }
    }

    return false;
}


/**
 * Searches for the layer that a drawable is registered to. If there is not
 * such layer returns NULL.
 *
 * @param toLocate - pointer to the Drawable to be located.
 * @return
 */

RenderLayer *RenderingManager::LocateDrawable(const Drawable *toLocate) const
{
    std::map< std::string, RenderLayer* >::const_iterator iter( m_layers.begin( ) ),
            end( m_layers.end( ) );

    RenderLayer *toReturn = NULL;

    for(; iter != end; ++iter )
    {
        if( iter->second->ContainsDrawable( toLocate ) )
        {
            toReturn = iter->second;
            break;
        }
    }

    return toReturn;
}


/**
 * Get Layer Count
 */

int RenderingManager::LayerCount() const
{
    return m_sortedLayers.size( );
}


/**
 * Searches for layer in a map and returns a pointer if found. Else returns NULL.
 */

RenderLayer *RenderingManager::FindLayer(const std::string& name)
{
    std::string key( name );
    
    StrToUpper( key );
    
    std::map<std::string, RenderLayer *>::iterator result = m_layers.find( key );

    if( result != m_layers.end( ) )
    {
        return result->second;
    }

    return NULL;
}


/*
 *  Returns position of a layer in sortedLayers. Returns -1 if layer
 *  doesn't exist in the vector.
 */

int RenderingManager::LayerIndex(RenderLayer *test)
{
    for( unsigned int i = 0; i < m_sortedLayers.size( ); ++i )
    {
        if( m_sortedLayers[i] == test )
        {
            return i;
        }
    }

    return -1;
}


/*
 * Updates the rendering manager's data
 */

void RenderingManager::Update()
{
    for( std::vector<RenderLayer *>::iterator iter = m_sortedLayers.begin( ); iter != m_sortedLayers.end( ); ++iter )
    {
        ( *iter )->SortDrawables( );
    }
}


/*
 * Insert layer between other layers.
 * movingIndex: the index of the layer that gets moved
 * positions: how many positions do we want the layer to be moved
 * dir: move layer (positions) positions LEFT or RIGHT in vector
 */

void RenderingManager::Insertion(int movingIndex, unsigned int positions, Direction dir)
{
    RenderLayer *temp = m_sortedLayers[movingIndex];

    switch( dir )
    {

    case RIGHT:
    {

        //position that we want to insert the layer located at movingIndex
        int maxPos = movingIndex + positions;

        //if it's bigger than total layers then bring layer to front
        if( maxPos >= LayerCount( ) )
        {
            maxPos = LayerCount( ) - 1;
        }

        for( int i = movingIndex; i < maxPos; ++i )
        {
            m_sortedLayers[i] = m_sortedLayers[i + 1];
        }

        m_sortedLayers[maxPos] = temp;
    }
        break;

    case LEFT:
    {
        int minPos = movingIndex - positions;

        if( minPos < 0 )
        {
            minPos = 0;
        }

        for( int i = movingIndex; i > minPos; --i )
        {
            m_sortedLayers[i] = m_sortedLayers[i - 1];
        }

        m_sortedLayers[minPos] = temp;
    }
        break;

    }
}


/**
 * Renders a line on the screen.
 *
 * @param start - start of the line
 * @param end - end of the line
 * @param r
 * @param g
 * @param b
 */

void RenderingManager::RenderLine(const Vector2F& start, const Vector2F& end, int r, int g, int b)
{
    int x0 = start.GetX( );
    int y0 = start.GetY( );
    int x1 = end.GetX( );
    int y1 = end.GetY( );

    if( m_camera != NULL )
    {
        const Math::Rect * cameraRect = m_camera->GetRect( );
        x0 -= cameraRect->GetLeft( );
        y0 -= cameraRect->GetTop( );
        x1 -= cameraRect->GetLeft( );
        y1 -= cameraRect->GetTop( );
    }

    gfx::drawLine( m_screen, x0, y0, x1, y1, sdl::Color( r, g, b ) );
}


/**
 * Renders a rectangle on the screen
 *
 * @param origin - origin point of the rectangle
 * @param width - width of the rectangle
 * @param height - height of the rectangle
 */

void RenderingManager::RenderRect(const Vector2F& origin, int width, int height, int r, int g, int b)
{
    int x0 = origin.GetX( );
    int y0 = origin.GetY( );

    if( m_camera != NULL )
    {
        const Math::Rect *cameraRect = m_camera->GetRect( );

        x0 -= cameraRect->GetLeft( );
        y0 -= cameraRect->GetTop( );
    }

    gfx::drawRect( m_screen, x0, y0, width, height, sdl::Color( r, g, b ) );
}


/**
 * Returns a pointer to the render's camera.
 */

Camera* RenderingManager::GetCamera()
{
    return m_camera;    
}


void RenderingManager::RenderText(const Vector2F& origin, const std::string& text, const sdl::Color& color)
{
    debugFont->RenderText( origin.GetX( ), origin.GetY( ), text, color, m_screen );
}


void RenderingManager::RenderText(const Vector2F& origin, const std::string& text)
{
    debugFont->RenderText( origin.GetX( ), origin.GetY( ), text, sdl::Colors::WHITE, m_screen );
}
